//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================


/*
 * This is based heavily on the code in FreeBSD-current, which was written
 * by Wolfgang Stanglmeier, and contains the following copyright:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include <ddk.h>
#include "osenv.h"
#include "os.h"
#include "compiler.h"

#include "xf86Procs.h"
#include "xf86_OSlib.h"
#include "xf86_PCI.h"


static pciConfigPtr pci_devp[MAX_PCI_DEVICES + 1] = {NULL, };

/* New PCI code */

static int pciConfigType = 0;
static int pciMaxDevice = 0;

static Boolean
pcibusCheck()
{
    CARD8 device;

    for (device = 0; device < pciMaxDevice; device++) {
        CARD32 id;
        id = pcibusRead(pcibusTag(0, device, 0), 0);
        if (id && id != 0xffffffff) {
            return TRUE;
        }
    }
    return 0;
}

static void
pcibusSetup()
{
    static Boolean setupDone = FALSE;
    CARD32 mode1Res1 = 0, mode1Res2 = 0, oldVal1 = 0;
    CARD8  mode2Res1 = 0, mode2Res2 = 0, oldVal2 = 0;
    int stages = 0;

    if (setupDone)
        return;

    setupDone = TRUE;

    switch (xf86PCIFlags) {

    case PCIProbe1: /* { */

        if (xf86Verbose > 1) {
            kprintf("PCI: Probing config type using method 1\n");
        }

        oldVal1 = Inl(PCI_MODE1_ADDRESS_REG);

#ifdef DEBUGPCI
        if (xf86Verbose > 2) {
            kprintf("Checking config type 1:\n" \
                "\tinitial value of MODE1_ADDR_REG is 0x%08x\n", oldVal1);
            kprintf("\tChecking that all bits in mask 0x7f000000 are clear\n");
        }
#endif

      /* Assuming config type 1 to start with */
        if ((oldVal1 & 0x7f000000) == 0) {

            stages |= 0x01;

            pciConfigType = 1;
            pciMaxDevice = PCI_CONFIG1_MAXDEV;

            Outl(PCI_MODE1_ADDRESS_REG, PCI_EN);


            mode1Res1 = Inl(PCI_MODE1_ADDRESS_REG);

#ifdef DEBUGPCI
            if (xf86Verbose > 2) {
                kprintf("\tValue read back from MODE1_ADDR_REG is 0x%08x\n", \
                    mode1Res1);
                kprintf("\tRestoring original contents of MODE1_ADDR_REG\n");
            }
#endif

            Outl(PCI_MODE1_ADDRESS_REG, oldVal1);

            if (mode1Res1) {

                stages |= 0x02;

#ifdef DEBUGPCI
                if (xf86Verbose > 2) {
                    kprintf("\tValue read back is non-zero, and indicates" \
                          " possible config type 1\n");
                }
#endif

                if (pcibusCheck()) {

#ifdef DEBUGPCI
                    if (xf86Verbose > 2)
                        kprintf("\tBus check Confirms this: ");
#endif

                    if (xf86Verbose > 1) {
                        kprintf("PCI: Config type is 1\n");
                    }
                    if (xf86Verbose > 2) {
                        kprintf("PCI: stages = 0x%02x, oldVal1 = 0x%08x, " \
                               "mode1Res1 = 0x%08x\n", \
                               stages, oldVal1, mode1Res1);
                    }
                    return;
                }

#ifdef DEBUGPCI
                if (xf86Verbose > 2) {
                    kprintf("\tBus check fails to confirm this, continuing " \
                           "type 1 check ...\n");
                }
#endif

            }

            stages |= 0x04;

#ifdef DEBUGPCI
            if (xf86Verbose > 2) {
                kprintf("\tWriting 0xff000001 to MODE1_ADDR_REG\n");
            }
#endif
            Outl(PCI_MODE1_ADDRESS_REG, 0xff000001);
            mode1Res2 = Inl(PCI_MODE1_ADDRESS_REG);

#ifdef DEBUGPCI
            if (xf86Verbose > 2) {
                kprintf("\tValue read back from MODE1_ADDR_REG is 0x%08x\n", \
                    mode1Res2);
                kprintf("\tRestoring original contents of MODE1_ADDR_REG\n");
            }
#endif

            Outl(PCI_MODE1_ADDRESS_REG, oldVal1);

            if ((mode1Res2 & 0x80000001) == 0x80000000) {

                stages |= 0x08;

#ifdef DEBUGPCI
                if (xf86Verbose > 2) {
                    kprintf("\tValue read back has only the msb set\n" \
                        "\tThis indicates possible config type 1\n");
                }
#endif

                if (pcibusCheck()) {

#ifdef DEBUGPCI
                    if (xf86Verbose > 2)
                        kprintf("\tBus check Confirms this: ");
#endif

                    if (xf86Verbose > 1) {
                        kprintf("PCI: Config type is 1\n");
                    }
                    if (xf86Verbose > 2) {
                        kprintf("PCI: stages = 0x%02x, oldVal1 = 0x%08x,\n" \
                            "\tmode1Res1 = 0x%08x, mode1Res2 = 0x%08x\n", \
                            stages, oldVal1, mode1Res1, mode1Res2);
                    }
                    return;
                }

#ifdef DEBUGPCI
                if (xf86Verbose > 2) {
                    kprintf("\tBus check fails to confirm this.\n");
                }
#endif

            }
      }

      if (xf86Verbose > 2) {
          kprintf("PCI: Standard check for type 1 failed.\n");
          kprintf("PCI: stages = 0x%02x, oldVal1 = 0x%08x,\n" \
              "\tmode1Res1 = 0x%08x, mode1Res2 = 0x%08x\n", \
              stages, oldVal1, mode1Res1, mode1Res2);
      }

      /* Try config type 2 */
      oldVal2 = Inb(PCI_MODE2_ENABLE_REG);
      if ((oldVal2 & 0xf0) == 0) {
          pciConfigType = 2;
          pciMaxDevice = PCI_CONFIG2_MAXDEV;

          Outb(PCI_MODE2_ENABLE_REG, 0x0e);
          mode2Res1 = Inb(PCI_MODE2_ENABLE_REG);
          Outb(PCI_MODE2_ENABLE_REG, oldVal2);

          if (mode2Res1 == 0x0e) {
              if (pcibusCheck()) {
                  if (xf86Verbose > 1) {
                      kprintf("PCI: Config type is 2\n");
                  }
                  return;
              }
          }
      }
      break; /* } */

    case PCIProbe2: /* { */

        /* The scanpci-style detection method */

        if (xf86Verbose > 1) {
            kprintf("PCI: Probing config type using method 2\n");
        }

        Outb(PCI_MODE2_ENABLE_REG, 0x00);
        Outb(PCI_MODE2_FORWARD_REG, 0x00);
        mode2Res1 = Inb(PCI_MODE2_ENABLE_REG);
        mode2Res2 = Inb(PCI_MODE2_FORWARD_REG);

        if (mode2Res1 == 0 && mode2Res2 == 0) {
            if (xf86Verbose > 1) {
                kprintf("PCI: Config type is 2\n");
            }
            pciConfigType = 2;
            pciMaxDevice = PCI_CONFIG2_MAXDEV;
            return;
        }

        oldVal1 = Inl(PCI_MODE1_ADDRESS_REG);
        Outl(PCI_MODE1_ADDRESS_REG, PCI_EN);
        mode1Res1 = Inl(PCI_MODE1_ADDRESS_REG);
        Outl(PCI_MODE1_ADDRESS_REG, oldVal1);
        if (mode1Res1 == PCI_EN) {
            if (xf86Verbose > 1) {
                kprintf("PCI: Config type is 1\n");
            }
            pciConfigType = 1;
            pciMaxDevice = PCI_CONFIG1_MAXDEV;
            return;
        }
        break; /* } */

    case PCIForceConfig1:

        if (xf86Verbose > 1) {
            kprintf("PCI: Forcing config type 1\n");
        }

        pciConfigType = 1;
        pciMaxDevice = PCI_CONFIG1_MAXDEV;
        return;

    case PCIForceConfig2:

        if (xf86Verbose > 1) {
            kprintf("PCI: Forcing config type 2\n");
        }

        pciConfigType = 2;
        pciMaxDevice = PCI_CONFIG2_MAXDEV;
        return;

    }

    /* No PCI found */

    pciConfigType = 0;
    pciMaxDevice = 0;
    if (xf86Verbose > 1) {
        kprintf("PCI: No PCI bus found\n");
    }
}

pciTagRec
pcibusTag(CARD8 bus, CARD8 cardnum, CARD8 func)
{
    pciTagRec tag;

    tag.cfg1 = 0;

    if (func > 7)
        return tag;

    switch (pciConfigType) {
    case 1:
        if (cardnum < PCI_CONFIG1_MAXDEV) {
            tag.cfg1 =
                PCI_EN |((CARD32)bus << 16) |
                ((CARD32)cardnum << 11) |
                ((CARD32)func << 8);
        }
        break;
    case 2:
        if (cardnum < PCI_CONFIG2_MAXDEV) {
            tag.cfg2.port    = 0xc000 | ((CARD16)cardnum << 8);
            tag.cfg2.enable  = 0xf0 | (func << 1);
            tag.cfg2.forward = bus;
        }
        break;
    }
    return tag;
}

static pciTagRec
pcibusFTag(pciTagRec tag, CARD8 func)
{
    if (func > 7) {
        return tag;
    }

    switch (pciConfigType) {
    case 1:
        tag.cfg1 &= ~0x700UL;
        tag.cfg1 |= ((CARD32)func << 8);
        break;
    case 2:
        tag.cfg2.enable = 0xf0 | (func << 1);
        break;
    }
    return tag;
}

CARD32
pcibusRead(pciTagRec tag, CARD32 reg)
{
    CARD32 addr, data = 0;

    if (!tag.cfg1) {
        return 0xffffffff;
    }

    switch (pciConfigType) {
    case 1:
        addr = tag.cfg1 | (reg & 0xfc);
        Outl(PCI_MODE1_ADDRESS_REG, addr);
        data = Inl(PCI_MODE1_DATA_REG);
        Outl(PCI_MODE1_ADDRESS_REG, 0);
        break;
    case 2:
        addr = tag.cfg2.port | (reg & 0xfc);
        Outb(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
        Outb(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
        data = Inl((CARD16)addr);
        Outb(PCI_MODE2_ENABLE_REG, 0);
        Outb(PCI_MODE2_FORWARD_REG, 0);
        break;
    }

    return data;
}

CARD16
pciReadWord(pciTagRec tag, CARD32 reg)
{
    CARD32 addr;
    CARD16 data = 0;

    if (!tag.cfg1) {
        return 0xff;
    }

    switch (pciConfigType) {
    case 1:
        addr = tag.cfg1 | (reg & 0xfc);
        Outl(PCI_MODE1_ADDRESS_REG, addr);
        data = Inw(PCI_MODE1_DATA_REG);
        Outl(PCI_MODE1_ADDRESS_REG, 0);
        break;
    case 2:
        addr = tag.cfg2.port | (reg & 0xfc);
        Outb(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
        Outb(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
        data = Inw((CARD16)addr);
        Outb(PCI_MODE2_ENABLE_REG, 0);
        Outb(PCI_MODE2_FORWARD_REG, 0);
        break;
    }

    return data;
}

CARD8
pciReadByte(pciTagRec tag, CARD32 reg)
{
    CARD32 addr;
    CARD8 data = 0;

    if (!tag.cfg1) {
        return 0xff;
    }

    switch (pciConfigType) {
    case 1:
        addr = tag.cfg1 | (reg & 0xfc);
        Outl(PCI_MODE1_ADDRESS_REG, addr);
        data = Inb(PCI_MODE1_DATA_REG);
        Outl(PCI_MODE1_ADDRESS_REG, 0);
        break;
    case 2:
        addr = tag.cfg2.port | (reg & 0xfc);
        Outb(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
        Outb(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
        data = Inb((CARD16)addr);
        Outb(PCI_MODE2_ENABLE_REG, 0);
        Outb(PCI_MODE2_FORWARD_REG, 0);
        break;
    }

    return data;
}

void
pcibusWrite(pciTagRec tag, CARD32 reg, CARD32 data)
{
    CARD32 addr;

    if (!tag.cfg1) {
        return;
    }

    switch (pciConfigType) {
    case 1:
        addr = tag.cfg1 | (reg & 0xfc);
        Outl(PCI_MODE1_ADDRESS_REG, addr);
        Outl(PCI_MODE1_DATA_REG, data);
        Outl(PCI_MODE1_ADDRESS_REG, 0);
        break;
    case 2:
        addr = tag.cfg2.port | (reg & 0xfc);
        Outb(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
        Outb(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
        Outl((CARD16)addr, data);
        Outb(PCI_MODE2_ENABLE_REG, 0);
        Outb(PCI_MODE2_FORWARD_REG, 0);
        break;
    }
}

void
pciWriteWord(pciTagRec tag, CARD32 reg, CARD16 data)
{
    CARD32 addr;

    if (!tag.cfg1) {
        return;
    }

    switch (pciConfigType) {
    case 1:
        addr = tag.cfg1 | (reg & 0xfc);
        Outl(PCI_MODE1_ADDRESS_REG, addr);
        Outw(PCI_MODE1_DATA_REG, data);
        Outl(PCI_MODE1_ADDRESS_REG, 0);
        break;

    case 2:
        addr = tag.cfg2.port | (reg & 0xfc);
        Outb(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
        Outb(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
        Outw((CARD16)addr, data);
        Outb(PCI_MODE2_ENABLE_REG, 0);
        Outb(PCI_MODE2_FORWARD_REG, 0);
        break;
    }
}

void
pciWriteByte(pciTagRec tag, CARD32 reg, CARD8 data)
{
    CARD32 addr;

    if (!tag.cfg1) {
        return;
    }

    switch (pciConfigType) {
    case 1:
        addr = tag.cfg1 | (reg & 0xfc);
        Outl(PCI_MODE1_ADDRESS_REG, addr);
        Outb(PCI_MODE1_DATA_REG, data);
        Outl(PCI_MODE1_ADDRESS_REG, 0);
        break;
    case 2:
        addr = tag.cfg2.port | (reg & 0xfc);
        Outb(PCI_MODE2_ENABLE_REG, tag.cfg2.enable);
        Outb(PCI_MODE2_FORWARD_REG, tag.cfg2.forward);
        Outb((CARD16)addr, data);
        Outb(PCI_MODE2_ENABLE_REG, 0);
        Outb(PCI_MODE2_FORWARD_REG, 0);
        break;
    }
}

static void
pciEnableIO(int scrnIndex)
{
    /* This is enough to ensure that full I/O is enabled */
    unsigned pciIOPorts[] = { PCI_MODE1_ADDRESS_REG };
    int numPciIOPorts = sizeof(pciIOPorts) / sizeof(pciIOPorts[0]);

    xf86ClearIOPortList(scrnIndex);
    xf86AddIOPorts(scrnIndex, numPciIOPorts, pciIOPorts);
    xf86EnableIOPorts(scrnIndex);
}

static void
pciDisableIO(int scrnIndex)
{
    xf86DisableIOPorts(scrnIndex);
    xf86ClearIOPortList(scrnIndex);
}

static Boolean
pciMfDev(CARD8 bus, CARD8 cardnum)
{
    pciTagRec tag0, tag1;
    CARD32 id0, id1;

    /* Detect a multi-function device that complies to the PCI 2.0 spec */

    tag0 = pcibusTag(bus, cardnum, 0);
    if (pcibusRead(tag0, PCI_HEADER_MISC) & PCI_HEADER_MULTIFUNCTION)
        return TRUE;

    /*
     * Now, to find non-compliant devices...
     * If there is a valid ID for function 1 and the ID for func 0 and 1
     * are different, or the base0 values of func 0 and 1 are differend,
     * then assume there is a multi-function device.
     */
    tag1 = pcibusTag(bus, cardnum, 1);
    id1 = pcibusRead(tag1, PCI_ID_REG);
    if (id1 != 0xffffffff) {
        id0 = pcibusRead(tag0, PCI_ID_REG);
        if (id0 != id1)
            return TRUE;
        if (pcibusRead(tag0, PCI_MAP_REG_START)
            != pcibusRead(tag1, PCI_MAP_REG_START))
            return TRUE;
    }
    return FALSE;
}

pciConfigPtr *
xf86scanpci(int scrnIndex)
{
    pciConfigRec pcr;
    int pcibusidx, pcinumbus, pcibuses[16];
    int idx = 0;

    if (pci_devp[0])
        return pci_devp;

    pciEnableIO(scrnIndex);

    /* Check for a PCI bus, and find the config type */
    pcibusSetup();

    if (pciConfigType == 0)
        return (pciConfigPtr *)NULL;

    pcibusidx = 0;
    pcibuses[0] = 0;
    pcinumbus = 1;

    do {
        for (pcr._cardnum = 0; pcr._cardnum < (CARD32)pciMaxDevice; pcr._cardnum++) {
            int maxfunc = 0;
            pciTagRec tag;

            pcr._bus = (CARD32)pcibuses[pcibusidx];

            if (pciMfDev(pcr._bus, pcr._cardnum))
                maxfunc = 7;

            tag = pcibusTag(pcr._bus, pcr._cardnum, 0);
            for (pcr._func = 0; pcr._func <= (CARD32)maxfunc; pcr._func++) {
                tag = pcibusFTag(tag, pcr._func);
                pcr._device_vendor = pcibusRead(tag, PCI_ID_REG);
                if (pcr._device_vendor == 0xffffffff)    /* nothing there */
                    continue;

                pcr._status_command = pcibusRead(tag, PCI_CMD_STAT_REG);
                pcr._class_revision = pcibusRead(tag, PCI_CLASS_REG);
                pcr._bist_header_latency_cache =
                    pcibusRead(tag, PCI_HEADER_MISC);
                pcr._base0 = pcibusRead(tag, PCI_MAP_REG_START);
                pcr._base1 = pcibusRead(tag, PCI_MAP_REG_START + 0x04);
                pcr._base2 = pcibusRead(tag, PCI_MAP_REG_START + 0x08);
                pcr._base3 = pcibusRead(tag, PCI_MAP_REG_START + 0x0c);
                pcr._base4 = pcibusRead(tag, PCI_MAP_REG_START + 0x10);
                pcr._base5 = pcibusRead(tag, PCI_MAP_REG_START + 0x14);
                pcr.rsvd1  = pcibusRead(tag, PCI_MAP_REG_START + 0x18);
                pcr.rsvd2  = pcibusRead(tag, PCI_MAP_REG_START + 0x1C);
                pcr._baserom = pcibusRead(tag, PCI_MAP_ROM_REG);
                pcr._max_min_ipin_iline = pcibusRead(tag, PCI_INTERRUPT_REG);
                pcr._user_config = pcibusRead(tag, PCI_REG_USERCONFIG);

                /* Check for PCI-PCI bridges */
                if (pcr._base_class == PCI_CLASS_BRIDGE &&
                    pcr._sub_class == PCI_SUBCLASS_BRIDGE_PCI) {
                    if (pcr._secondary_bus_number > 0) {
                        pcibuses[pcinumbus++] = pcr._secondary_bus_number;
                    }
                }

                if (idx >= MAX_PCI_DEVICES)
                    continue;

                if ((pci_devp[idx] = (pciConfigPtr)xalloc(sizeof(pciConfigRec)))
                    == (pciConfigPtr)NULL) {
                    pciDisableIO(scrnIndex);
                    return (pciConfigPtr *)NULL;
                }

                memcpy(pci_devp[idx++], &pcr, sizeof(pciConfigRec));
                pci_devp[idx] = (pciConfigPtr)NULL;
                if (xf86Verbose > 1) {
                    kprintf("PCI: Bus 0x%x Card 0x%02x Func 0x%x ID 0x%04x," \
                        "0x%04x Rev 0x%02x Class 0x%02x,0x%02x\n", \
                        pcr._bus, pcr._cardnum, pcr._func, pcr._vendor, \
                        pcr._device, pcr._rev_id, pcr._base_class, \
                        pcr._sub_class);
                }
            }
        }
    } while (++pcibusidx < pcinumbus);

    pciDisableIO(scrnIndex);
    return pci_devp;
}

void
xf86writepci(int scrnIndex, int bus, int cardnum, int func, int reg,
         CARD32 mask, CARD32 value)
{
    pciTagRec tag;
    CARD32 data;

    pciEnableIO(scrnIndex);

    /* Check for a PCI bus, and find the config type */
    pcibusSetup();

    if (pciConfigType == 0)
        return;

    tag = pcibusTag(bus, cardnum, func);
    data = pcibusRead(tag, reg) & ~mask | (value & mask);
    pcibusWrite(tag, reg, data);

    if (xf86Verbose > 2) {
        kprintf("PCI: xf86writepci: Bus=0x%x Card=0x%x Func=0x%x Reg=0x%02x " \
            "Mask=0x%08x Val=0x%08x\n", \
            bus, cardnum, func, reg, mask, value);
    }

    pciDisableIO(scrnIndex);
}

void
xf86cleanpci()
{
    int idx = 0;

    while (pci_devp[idx])
        Xfree((pointer)pci_devp[idx++]);

    pci_devp[0] = (pciConfigPtr)NULL;
}
